﻿#region Creditos

/* Autor: Edilberto Sánchez Forero	*/
/* Email: esanchezfo@gmail.com		*/
/* Licencia: LGPL, Software Libre	*/
/* País: Colombia, Bogotá			*/

#endregion

#region Librerias

using System;
using System.Xml;
using System.IO;
using System.Collections.Generic;

using ESF.General.Factory;
using ESF.General;
using ESF.Object.XmlRepresenter.Extensions.Description;
using ESF.General.Text;

#endregion


namespace ESF.Object.XmlRepresenter
{
	// TODO: Validar contra el origen de datos de la connectionstring la estructura existente y lanzar los cambios
	// encontrados para determinar si se adapta el xml a la db o al revez
	public class XmlObjectSetManager
	{	
		#region Miembros
		
		private List<XmlObjectSet> _ObjectSets;
				
		private List<XmlObject> _SortedObjects;
				
		private bool _ClosedForAdd;
		
		#endregion
		
		
		#region Propiedades
		
		public List<XmlObjectSet> ObjectSets 
		{
			get
			{
				this.Close ();
				return this._ObjectSets; 
			} 
		}
		
		
		public XmlObjectSet this [string pstrObjectSetName]
		{
			// TODO: Soportar SET
			get 
			{
				return this._ObjectSets.Find (os => os.Name == pstrObjectSetName);
			}
		}
		
		
		public List<XmlObject> SortedObjects
		{
			get
			{
				if (this._ClosedForAdd)
				{
					return this._SortedObjects;
				}
				else
				{
					throw new GenericException (GenericException.CRI_OBJECTCOLLECTION_MUSTBECLOSED, "No se pueden obtener los objetos ordenados hasta tanto sea realizado el cierre");
				}
			}
		}
		
		
		public string [] SetNames
		{
			get
			{
				int intI;
				
				string [] strSetNames;
				
				
				strSetNames = new string [this._ObjectSets.Count];
				
				for (intI = 0; intI < this._ObjectSets.Count; intI ++)
				{
					strSetNames [intI] = this._ObjectSets [intI].Name;
				}
				
				return strSetNames;
			}
		}
		
		#endregion
		
		
		#region Constructor
		
		public XmlObjectSetManager ()
		{
			this._ObjectSets = new List<XmlObjectSet> ();
			
			this._SortedObjects = new List<XmlObject> ();
		}
		
		#endregion
		
		
		#region Funciones
			
		public void Add (string pstrFileName)
		{
			this.Add (new XmlObjectSet (pstrFileName));
		}
		
		
		public void Add (XmlObjectSet pobjObjectSet)
		{
			if (this._ClosedForAdd)
			{
				throw new GenericException (GenericException.CRI_OBJECTCOLLECTION_CLOSED, string.Format ("No se acepta el conjunto {0} la coleccion esta cerrada", pobjObjectSet.Name));
			}
		
			if (this._ObjectSets.Contains (pobjObjectSet))
			{
				throw new GenericException (GenericException.CRI_OBJECT_ALREADYINCOLLECTION, string.Format ("Ya hay registrado un el conjunto de objetos con el mismo nombre: {0}", pobjObjectSet.Name));
			}
		
			this._ObjectSets.Add (pobjObjectSet);
		}
		
		
		public void Clear()
		{
			this._ClosedForAdd = false;
			
			this._ObjectSets.Clear ();
			
			this._SortedObjects.Clear ();
		}
		
		
		public void Close ()
		{
			if (this._ClosedForAdd) { return; }
		
			this._ClosedForAdd = true;
			
			this.SortSets ();
			
			this.GenerateSortedObjects ();
			
			this.LoadTraslatedDescriptions ();
		}
				
		
		private void CheckCyclicDependencies ()
		{
			string strPathDep;
			
		
			strPathDep = string.Empty;
		
			foreach (XmlObjectSet objObjSet in this._ObjectSets)
			{
				foreach (XmlObject objObject in objObjSet.NoArrayComplexObjects)
				{
					foreach (XmlMember objMember in objObject.CustomMembers.FindAll (m => !string.IsNullOrEmpty (m.FromSet)))
					{
						if (this.CheckCyclicDependency (objObjSet.Name, objMember.FromSet, objMember.CustomType, ref strPathDep))
						{
							throw new GenericException (GenericException.CRI_OBJECT_CYLICDEPEDENCY, string.Format ("El miembro {0} es ciclicamente referenciado en {1}", strPathDep, objMember));
						}
					}
				}
			}
		}
		
		
		private bool CheckCyclicDependency (string pstrOriginSetName, string pstrRefSetName, string pstrCustomType, ref string pstrPathDep)
		{
			foreach (XmlMember objMember in this [pstrRefSetName][pstrCustomType].CustomMembers.FindAll (m => !string.IsNullOrEmpty (m.FromSet)))
			{
				if (!string.IsNullOrEmpty (objMember.FromSet))
				{
					if (objMember.FromSet == pstrOriginSetName)
					{
						pstrPathDep = string.Format ("{0}.{1}", pstrRefSetName, pstrCustomType);
						return true;
					}
					else if (this.CheckCyclicDependency (pstrOriginSetName, objMember.FromSet, objMember.CustomType, ref pstrPathDep))
					{
						return true;
					}
				}
			}
			
			return false;
		}
		
		
		public XmlObject GetObjectBy (XmlMember pobjMember)
		{
			if (pobjMember.IsBasic)
			{
				throw new GenericException (GenericException.CRI_INVALID_PARAMETER, string.Format ("No se puede obtener un objeto registrado de un miembro basico como {0}", pobjMember.FullName));
			}
		
			return this [pobjMember.ApplicableFromSet] [pobjMember.CustomType];
		}
		
		
		public void GetObjectBy (XmlMember pobjMember, out XmlObject pobjRefObject, out XmlObject pobjExtRefObject)
		{
			pobjRefObject = this [pobjMember.ApplicableFromSet] [pobjMember.CustomType];
			
			pobjExtRefObject = this.GetObjectExtRefByRef (pobjRefObject);
		}
		
		
		public XmlObject GetObjectExtRefByRef (XmlObject pobjRefObject)
		{
			XmlObject pobjExtRefObject;
			
		
			if (pobjRefObject.IsArray)
			{
				pobjExtRefObject = this [pobjRefObject.SetName] [pobjRefObject.Type];
			}
			else
			{
				pobjExtRefObject = null;
			}
			
			return pobjExtRefObject;
		}
		
		
		public void TraslateRefValueType (XmlMember pobjMember, out ValueTypes penuIDOrMemberValueType, out ushort? pshrIDOrMemberLength)
		{
			XmlObject objRef;
			XmlObject objExtRef;
			
			
			if (pobjMember.Type == ValueTypes.Custom) 
			{
				objRef = this.GetObjectBy (pobjMember);
				
				if (objRef.IsArray)
				{
					objExtRef = this [objRef.SetName][objRef.Type];
					
					penuIDOrMemberValueType = (ValueTypes) objExtRef.IDType;
					pshrIDOrMemberLength = objExtRef.IDMaxLength;					
				}
				else
				{
					penuIDOrMemberValueType = (ValueTypes) objRef.IDType;
					pshrIDOrMemberLength = objRef.IDMaxLength;
				}
			}
			else
			{
				penuIDOrMemberValueType = pobjMember.Type.Value;
				pshrIDOrMemberLength = pobjMember.ApplicableMaxLength;
			}
		}
		
		#endregion
		
		
		#region Gestion de Descripciones
		
		private void LoadTraslatedDescriptions ()
		{
			string strFileName;
			string strISOLanguageCode;
			
			bool blnSettedDefault;
			bool blnDefault;
						
			XmlObjectDescription objObjectDescription;
			
			XmlMemberDescription objMemberDescription;
			
			XmlHelper xmlEx;
			
			XmlTextReader xmlReader;
			
			
			objObjectDescription = null;
			
			foreach (XmlObjectSet objObjectSet in this._ObjectSets)
			{
				strFileName = Path.Combine (Path.Combine (Path.GetDirectoryName (objObjectSet.FileName), "Description"), objObjectSet.Name + ".xml");
					
				if (!File.Exists (strFileName)) { return; }
				
				#region Carga del archivo
				
				try
				{
					xmlReader = new XmlTextReader (strFileName);
				}			
				catch (Exception pexcEx)
				{
					throw new GenericException (GenericException.CRI_FILE_ERROR, string.Format ("El archivo {0} no pudo ser leido", strFileName), pexcEx);
				}
				
				#endregion
				
				xmlEx = new XmlHelper ();
				xmlEx.ObjectName = "Descripción Traducida para el conjunto de objetos " + objObjectSet.Name;
				xmlEx.EvaluatedNodeReader = xmlReader;
				
				blnSettedDefault = false;
				blnDefault = false;
				strISOLanguageCode = null;
				
				try
				{
					while (xmlReader.Read ())
					{
						switch (xmlReader.NodeType)
						{
							case XmlNodeType.Element:
								
								// El nivel de la traduccion (traslate)
								if (xmlReader.Depth == 1)
								{
									blnDefault = xmlEx.GetAttributeValueBool (false, "default", "Indicador de lenguaje por defecto", false).Value;
									
									if (blnDefault) 
									{
										if (blnSettedDefault)
										{
											throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, string.Format ("Solo una descripcion ademas del lenguaje {0} traducida puede ser la de por defecto", objObjectDescription.ISOLanguageCode));
										}
										else
										{
											blnSettedDefault = true;
										}
									}
									
									strISOLanguageCode = xmlEx.GetAttributeValue (true, "isoLanguageCode", "Código del Lenguaje de la descripción", XmlObject.ISOLANGUAGECODE_LENGTH, XmlObject.ISOLANGUAGECODE_LENGTH);
								}
								
								// El nivel de cada objeto
								else if (xmlReader.Depth == 2)
								{
									if (objObjectDescription != null && !objObjectSet [objObjectDescription.ObjectName].IsArray)
									{
										objObjectSet [objObjectDescription.ObjectName].AddDescription (objObjectDescription);
									}
									
									objObjectDescription = new XmlObjectDescription (ref xmlReader, blnDefault, strISOLanguageCode);
								}
								
								// El nivel de cada miembro
								else if (xmlReader.Depth == 3)
								{
									objMemberDescription = new XmlMemberDescription (ref xmlReader, objObjectDescription.ObjectName, blnDefault, strISOLanguageCode);	
									
									objObjectSet [objObjectDescription.ObjectName] [objMemberDescription.MemberName].AddDescription (objMemberDescription);
								}
								
							break;
						}
					}
					
					if (objObjectDescription != null && !objObjectSet [objObjectDescription.ObjectName].IsArray)
					{
						objObjectSet [objObjectDescription.ObjectName].AddDescription (objObjectDescription);
						objObjectDescription = null;
					}
				}
				catch (Exception excEx)
				{
					throw new GenericException (GenericException.CRI_READ_ERROR, string.Format ("No se completo la lectura del archivo {0}", strFileName), excEx);
				}
				finally
				{
					xmlReader.Close ();
				}
			}
			
			this.CloseDescriptions ();
		}
		
		
		private void CloseDescriptions ()
		{
			foreach (XmlObjectSet objSet in this._ObjectSets)
			{
				foreach (XmlObject objObj in objSet.Objects)
				{
					objObj.CloseDescriptions (this);
				}
			}
		}
		
		#endregion
		
		
		#region Organizacion de relaciones entre objetos

		private void SortSets ()
		{
			int intI;
			int intIndexFound;
			
			XmlObjectSet objCurrentObjectSet;
			
			XmlMember objFailMember;
			
		
			if (this._ObjectSets.Count < 2) { return; }
		
			this.CheckCyclicDependencies ();
		
			for (intI = 0; intI < this._ObjectSets.Count; intI ++)
			{
				objCurrentObjectSet = this._ObjectSets [intI];
			
				foreach (string strObjectSetDepName in objCurrentObjectSet.ExternalObjectSetsNamesDependencies)
				{
					intIndexFound = this._ObjectSets.FindIndex (os => os.Name == strObjectSetDepName);
					
					if (intIndexFound == -1)
					{
						objFailMember = objCurrentObjectSet.ExternalDefinedObjectMembers.Find (edm => edm.FromSet == strObjectSetDepName);
					
						throw new GenericException (GenericException.CRI_OBJECT_INEXISTENT_DEPENDENCY, 
							string.Format ("El miembro {2}.{3} del tipo {4} en el conjunto {0} se referencia como del conjunto {1} pero este no esta registrado", 
								objCurrentObjectSet.Name, strObjectSetDepName, 
								objFailMember.Object.Name, objFailMember.Name, objFailMember.CustomType));
					}
					
					if (intIndexFound > 0 && intIndexFound > intI)
					{
						this._ObjectSets.RemoveAt (intI);
						this._ObjectSets.Insert (intIndexFound, objCurrentObjectSet);
						
						intI = -1;
					}
				}
			}
		}
		
		
		// TODO: Soportar eventos de cambio para revalidar la estructura del conjunto
		public static void ReviewSet (ref List<XmlObjectSet> pobjObjectSets)
		{
			XmlObjectSet objExploreObjectSet;
			
				
			foreach (XmlObjectSet objObjectSet in pobjObjectSets.FindAll (os => os.ExternalDefinedObjectMembers.Count > 0))
			{
				foreach (XmlMember objExtMember in objObjectSet.ExternalDefinedObjectMembers)
				{
					objExploreObjectSet = pobjObjectSets.Find (os => os.Name == objExtMember.FromSet);
					
					if (objExploreObjectSet == null)
					{
						throw new GenericException (GenericException.CRI_INVALID_REFERENCE, string.Format ("El miembro {0} del tipo {1} referencia un conjunto de objetos {2} inexistente", objExtMember.Name, objExtMember.CustomType, objExtMember.FromSet));
					}
					
					if (objExploreObjectSet [objExtMember.CustomType] == null)
					{
						throw new GenericException (GenericException.CRI_INVALID_REFERENCE, string.Format ("El miembro {0} del tipo {1} no tiene soporte en el conjunto de objetos {2}", objExtMember.Name, objExtMember.CustomType, objExtMember.FromSet));
					}
				}
			}
		}
		
		
		public void GenerateSortedObjects ()
		{	
			//int intI;
		
			CpsComposite<XmlObject> objGeneralRoot;
			
			List<XmlObject> objComplexObjects;
			List<XmlObject> objBasicObjects;
			List<XmlObject> objNoDepComplexObjects;
			
			XmlObject objCurrentRefObject;
			
			
			this.Close ();
			
			XmlObjectSetManager.ReviewSet (ref this._ObjectSets);
			
			// Los objetos ya tienen definido su nivel zero si no tienen dependencias, IsBasic = true
			// Queda por identificar los niveles de dependencia para los demas objetos
			// lo cual se realiza a continuacion:
			
			#region Recorrer los objetos de los conjuntos que no sean colecciones		
			
			foreach (XmlObjectSet objObjectSet in this._ObjectSets)
			{
				foreach (XmlObject objObject in objObjectSet.Objects.FindAll (o => !o.IsBasic))
				{			
					// Al objeto actual se le agrega en el listado y se le preceden sus objetos dependencias
					if (objObject.IsArray)
					{
						// Al ser un arreglo su tipo debe estar definido en su propio conjunto
						objCurrentRefObject = objObjectSet [objObject.Type];
						
						// Se agrega el tipo del cual depende
						objCurrentRefObject.IncreaseLevel ();
					}
					
					// Se recorren los miembros de los objetos que dependen de otros
					foreach (XmlMember pobjMember in objObject.CustomMembers)
					{
						if (string.IsNullOrEmpty (pobjMember.FromSet))
						{
							objCurrentRefObject = objObjectSet [pobjMember.CustomType];
						}
						else
						{
							objCurrentRefObject = this [pobjMember.FromSet][pobjMember.CustomType];
						}
						
						objCurrentRefObject.IncreaseLevel ();
					}			
				}
			}
			#endregion
			
			
			#region Filtrado de objetos por tipo
			
			objComplexObjects = new List<XmlObject> ();
			objBasicObjects = new List<XmlObject> ();
			objNoDepComplexObjects = new List<XmlObject> ();
			
			// Se filtran los objetos basicos, y aquellos que no tienen dependencias para dejar
			// asi para el analisis aquellos que tienen dependencias entre si
			foreach (XmlObjectSet objObjectSet in this._ObjectSets)
			{
				objBasicObjects.AddRange (objObjectSet.Objects.FindAll (o => o.IsBasic));
				objNoDepComplexObjects.AddRange (objObjectSet.Objects.FindAll (o => !o.IsBasic && o.Level == 0 || o.IsArray));
				objComplexObjects.AddRange (objObjectSet.Objects.FindAll (o => !o.IsBasic && o.Level > 0 && !o.IsArray));
			}
		
			#endregion
			
			
			#region Deteccion de relaciones de arbol entre objetos complejos
			
			objGeneralRoot = new CpsComposite<XmlObject> ();
			
			foreach (XmlObject objComplexObject in objComplexObjects)
			{
				this.GenerateObjectTreeLevel (objGeneralRoot, objGeneralRoot, objComplexObject, objComplexObjects);
			}
			
			// this.AdjustCollectionLevels (objGeneralRoot);		
			this.RecollectObjectTreeLevels (objGeneralRoot, ref objComplexObjects);
			
			#endregion
			
			this._SortedObjects.Clear ();
			
			this._SortedObjects.AddRange (objBasicObjects);
			
			this._SortedObjects.AddRange (objComplexObjects);
			
			this._SortedObjects.AddRange (objNoDepComplexObjects);
		}
		
		
		//private void AdjustCollectionLevels (CpsComposite<XmlObject> pobjLevel)
		//{
		//    CpsComposite<XmlObject> objNewLevel;
			
		
		//    if (pobjLevel.Object != null)
		//    {
		//        foreach (CpsComposite<XmlObject> objLevel in pobjLevel.Children)
		//        {
		//            if (pobjLevel.Object.SetName == objLevel.Object.SetName &&
		//                pobjLevel.Object.Type == objLevel.Object.Name)
		//            {
		//                objNewLevel = new CpsComposite<XmlObject> (pobjLevel.Object);
						
		//                pobjLevel.Object = objLevel.Object;
		//                pobjLevel.Children [0].Object = objNewLevel.Object;
		//            }
		//        }
		//    }
		
		//    foreach (CpsComposite<XmlObject> objLevel in pobjLevel.Children)
		//    {
		//        this.AdjustCollectionLevels (objLevel);
		//    }
		//}
		
		
		private void RecollectObjectTreeLevels (CpsComposite<XmlObject> pobjLevel, ref List<XmlObject> pobjObjectList)
		{
		    if (pobjLevel.Object != null && !pobjObjectList.Contains (pobjLevel.Object))
		    {
		        pobjObjectList.Insert (0, pobjLevel.Object);
		    }
			
		    foreach (CpsComposite<XmlObject> objChild in pobjLevel.Children)
		    {
		        this.RecollectObjectTreeLevels (objChild, ref pobjObjectList);
		    }
		}
		
		
		private void GenerateObjectTreeLevel (CpsComposite<XmlObject> pobjGeneralRoot, CpsComposite<XmlObject> pobjRoot, XmlObject objCurrentObject, List<XmlObject> objComplexObjects)
		{
			XmlObject objCurrentAnalysis;
			
			CpsComposite<XmlObject> objCurrentRoot;
			CpsComposite<XmlObject> objNewRoot;
			
			
			objCurrentRoot = null;
		
			// Si ya esta en el arbol ya fue procesado al igual que sus objetos dependientes
			if (pobjGeneralRoot.Search (objCurrentObject, ref objCurrentRoot))
			{
				// Si el objeto ya esta en la raiz o corresponde al actual no se hace nada
				if (pobjRoot.Object == null || pobjRoot.Object == objCurrentRoot.Object) { return; }
				
				objNewRoot = new CpsComposite<XmlObject> (objCurrentRoot.Object);
				objNewRoot.Children.AddRange (objCurrentRoot.Children);
				
				if (objCurrentRoot.Parent.Object == null)
				{
					objCurrentRoot.Object = null;
					objCurrentRoot.Children.Clear ();
				}
				
				pobjRoot.Add (objNewRoot);
				return;
			}
			else
			{
				//if (objCurrentObject.IsArray)
				//{
				//    objCurrentAnalysis = this [objCurrentObject.SetName][objCurrentObject.Type];
					
				//    // Si el objeto esta dentro de los complejos se analiza
				//    if (objComplexObjects.Contains (objCurrentAnalysis))
				//    {
				//        if (pobjGeneralRoot.Search (objCurrentAnalysis, ref objCurrentRoot))
				//        {
				//            objNewRoot = new CpsComposite<XmlObject> (objCurrentRoot.Object);
				//            objNewRoot.Children.AddRange (objCurrentRoot.Children);
							
				//            objCurrentRoot.Object = objCurrentObject;							
				//            objCurrentRoot.Children.Clear ();
				//            objCurrentRoot.Add (objNewRoot);
				//            return;
				//        }
				//    }
				//}		
								
				// Se realiza una busqueda por objetos que dependan del objeto actual
				if (pobjGeneralRoot.CustomSearch (ref objCurrentRoot, 
					delegate (XmlObject pobjObject) 
					{
						foreach (XmlMember objMember in pobjObject.CustomMembers)
						{
							objCurrentAnalysis = this.GetObjectBy (objMember);
							
							if (objCurrentAnalysis == objCurrentObject)
							{
								return true;
							}
						}
						
						return false;
					}))
				{
					objNewRoot = new CpsComposite<XmlObject> (objCurrentRoot.Object);
					objNewRoot.Children.AddRange (objCurrentRoot.Children);
					
					objCurrentRoot.Object = objCurrentObject;							
					objCurrentRoot.Children.Clear ();
					objCurrentRoot.Add (objNewRoot);
					return;
				}
			
				objCurrentRoot = new CpsComposite<XmlObject> (objCurrentObject);
				pobjRoot.Add (objCurrentRoot);
			}
		
			// Si es un arreglo no tiene miembros pero si un objeto que por su tipo puede ser un tipo complejo
			//if (objCurrentObject.IsArray)
			//{
			//    objCurrentAnalysis = this [objCurrentObject.SetName][objCurrentObject.Type];
				
			//    // Si el objeto esta dentro de los complejos se analiza
			//    if (objComplexObjects.Contains (objCurrentAnalysis))
			//    {
			//        this.GenerateObjectTreeLevel (pobjGeneralRoot, objCurrentRoot, objCurrentAnalysis, objComplexObjects);
			//    }
			//}
			//else
			//{
				foreach (XmlMember objMember in objCurrentObject.CustomMembers)
				{
					if (string.IsNullOrEmpty (objMember.FromSet))
					{
						objCurrentAnalysis = this [objCurrentObject.SetName][objMember.CustomType];						
					}
					else					
					{
						objCurrentAnalysis = this [objMember.FromSet][objMember.CustomType];
					}
					
					// Si el objeto esta dentro de los complejos se analiza
					if (objComplexObjects.Contains (objCurrentAnalysis))
					{
						this.GenerateObjectTreeLevel (pobjGeneralRoot, objCurrentRoot, objCurrentAnalysis, objComplexObjects);
					}
				}
			//}
		}
		
		#endregion			
	}
}
